/*
 * Created on 2005-3-21
 *
 */
package com.hd123.rumba.request;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;

import com.hd123.rumba.runtime.FormatSettings;
import com.hd123.rumba.util.StringUtil;

/**
 * @author caili
 */
public class MultipartRequest {

  public static final int DEFAULT_MAX_POST_SIZE = 0x100000;
  private HttpServletRequest req;
  private File dir;
  private int maxSize;
  private Hashtable parameters;
  private Hashtable files;
  //////////////////////
  private Hashtable typedFiles;

  public Enumeration getTypedFileKeys() {
    return typedFiles.keys();
  }

  public File getTypedFile(String s) {
    return (File) typedFiles.get(s);
  }

  //////////////////////

  public Enumeration getParameterNames() {
    return parameters.keys();
  }

  public Enumeration getFileNames() {
    return files.keys();
  }

  public String[] getParameter(String s) {
    try {
      List list = (List) parameters.get(s);
      return (String[]) list.toArray(new String[0]);
    } catch (Exception exception) {
      return null;
    }
  }

  public String getFilesystemName(String s) {
    try {
      UploadedFile uploadedfile = (UploadedFile) files.get(s);
      return uploadedfile.getFilesystemName();
    } catch (Exception exception) {
      return null;
    }
  }

  public String getContentType(String s) {
    try {
      UploadedFile uploadedfile = (UploadedFile) files.get(s);
      return uploadedfile.getContentType();
    } catch (Exception exception) {
      return null;
    }
  }

  public File getFile(String s) {
    try {
      UploadedFile uploadedfile = (UploadedFile) files.get(s);
      return uploadedfile.getFile();
    } catch (Exception exception) {
      return null;
    }
  }

  protected void readRequest() throws IOException, FileTooLargeException {
    String s = req.getContentType();
    if (s == null || !s.toLowerCase().startsWith("multipart/form-data"))
      throw new IOException("posted content type is not multipart/form-data");
    int i = req.getContentLength();
    if (i > maxSize)
      throw new FileTooLargeException("提交的内容长度" + i + "超出了最大限制长度" + maxSize);
    String s1 = extractBoundary(s);
    if (s1 == null)
      throw new IOException("separation boundary was not specified");
    String encoding = req.getCharacterEncoding();
    if (encoding == null)
      encoding = StringUtil.ENCODING_CHARSET;
    MultipartInputStreamHandler multipartinputstreamhandler = new MultipartInputStreamHandler(
        req.getInputStream(), s1, i, encoding);
    String s2 = multipartinputstreamhandler.readLine();
    if (s2 == null)
      throw new IOException("corrupt form data: premature ending");
    if (!s2.startsWith(s1))
      throw new IOException("corrupt form data: no leading boundary");
    for (boolean flag = false; !flag; flag = readNextPart(
        multipartinputstreamhandler, s1))
      ;
  }

  protected boolean readNextPart(
      MultipartInputStreamHandler multipartinputstreamhandler, String s)
      throws IOException {
    String s1 = multipartinputstreamhandler.readLine();
    if (s1 == null)
      return true;
    // s1 = StringUtil.decodePathInfo(s1);
    Logger.getLogger(this.getClass()).debug("s1=" + s1);
    String as[] = extractDispositionInfo(s1);
    // String contentdisposition = as[0]; // contentdisposition
    String name = as[1]; // name
    String filename = as[2]; // filename
    s1 = multipartinputstreamhandler.readLine();
    if (s1 == null)
      return true;
    String contenttype = extractContentType(s1);
    if (contenttype != null) {
      String s2 = multipartinputstreamhandler.readLine();
      if (s2 == null || s2.length() > 0)
        throw new IOException("malformed line after content type: " + s2);
    } else {
      contenttype = "application/octet-stream";
    }
    if (filename == null) {
      String s7 = readParameter(multipartinputstreamhandler, s);
      if (parameters.containsKey(name)) {
        List list = (List) parameters.get(name);
        list.add(s7);
      } else {
        ArrayList arraylist = new ArrayList();
        arraylist.add(s7);
        parameters.put(name, arraylist);
      }
    } else {
      readAndSaveFile(multipartinputstreamhandler, s, filename);
      UploadedFile uploadedFile = null;
      if (filename.equals("unknown"))
        uploadedFile = new UploadedFile(null, null, null);
      else
        uploadedFile = new UploadedFile(dir.toString(), filename, contenttype);
      files.put(filename, uploadedFile);
      //////////////////////
      typedFiles.put(name, (File) uploadedFile.getFile());
      //////////////////////
      if (filename.startsWith("unknown.")) {
        ((File)uploadedFile.getFile()).delete();
      }
    }
    return false;
  }

  protected String readParameter(
      MultipartInputStreamHandler multipartinputstreamhandler, String s)
      throws IOException {
    StringBuffer stringbuffer = new StringBuffer();
    String s1;
    while ((s1 = multipartinputstreamhandler.readLine()) != null) {
      if (s1.startsWith(s))
        break;
      stringbuffer.append(s1 + "\r\n");
    }
    if (stringbuffer.length() == 0) {
      return null;
    } else {
      stringbuffer.setLength(stringbuffer.length() - 2);
      return stringbuffer.toString();
    }
  }

  protected void readAndSaveFile(
      MultipartInputStreamHandler multipartinputstreamhandler, String s,
      String s1) throws IOException {
    File file = new File(dir + File.separator + s1);
    FileOutputStream fileoutputstream = new FileOutputStream(file);
    BufferedOutputStream bufferedoutputstream = new BufferedOutputStream(
        fileoutputstream, 8192);
    byte abyte0[] = new byte[8192];
    boolean flag = false;
    int i;
    while ((i = multipartinputstreamhandler.readLine(abyte0, 0, abyte0.length)) != -1) {
      if (i > 2 && abyte0[0] == 45 && abyte0[1] == 45) {
        String s2 = new String(abyte0, 0, i, multipartinputstreamhandler
            .getCharacterEncoding());
        if (s2.startsWith(s))
          break;
      }
      if (flag) {
        bufferedoutputstream.write(13);
        bufferedoutputstream.write(10);
        flag = false;
      }
      if (i >= 2 && abyte0[i - 2] == 13 && abyte0[i - 1] == 10) {
        bufferedoutputstream.write(abyte0, 0, i - 2);
        flag = true;
      } else {
        bufferedoutputstream.write(abyte0, 0, i);
      }
    }
    bufferedoutputstream.flush();
    bufferedoutputstream.close();
    fileoutputstream.close();
  }

  private String extractBoundary(String s) {
    int i = s.indexOf("boundary=");
    if (i == -1) {
      return null;
    } else {
      String s1 = s.substring(i + 9);
      s1 = "--" + s1;
      return s1;
    }
  }

  private String[] extractDispositionInfo(String s) throws IOException {
    String as[] = new String[3];
    String s1 = s;
    s = s1.toLowerCase();
    int i = s.indexOf("content-disposition");
    int j = s.indexOf(";");
    if (i == -1 || j == -1)
      throw new IOException("content disposition corrupt: " + s1);
    String s2 = s.substring(i + 21, j);
    if (!s2.equals("form-data"))
      throw new IOException("invalid content disposition: " + s2);
    i = s.indexOf("name=\"", j);
    j = s.indexOf("\"", i + 7);
    if (i == -1 || j == -1)
      throw new IOException("content dispostion corrupt: " + s1);
    String s3 = s1.substring(i + 6, j);
    String s4 = null;
    i = s.indexOf("filename=\"", j + 2);
    j = s.indexOf("\"", i + 10);
    if (i != -1 && j != -1) {
      s4 = s1.substring(i + 10, j);
      int k = Math.max(s4.lastIndexOf(47), s4.lastIndexOf(92));
      if (k > -1)
        s4 = s4.substring(k + 1);
      if (s4.equals(""))
        s4 = "unknown";
    }
    as[0] = s2; // contentdisposition
    as[1] = s3; // name
    ////////////////////////
    as[2] = s4 == null ? null : s4
        + new Double(java.lang.Math.random()).toString().substring(1);
    // filename
    ////////////////////////
    return as;
  }

  private String extractContentType(String s) throws IOException {
    String s1 = null;
    String s2 = s;
    s = s2.toLowerCase();
    if (s.startsWith("content-type")) {
      int i = s.indexOf(" ");
      if (i == -1)
        throw new IOException("content type corrupt: " + s2);
      s1 = s.substring(i + 1);
    } else if (s.length() != 0)
      throw new IOException("malformed line after disposition: " + s2);
    return s1;
  }

  /**
   * 上传大小限制为2M
   * 
   * @param servletrequest
   * @param s
   *          上传后暂存的文件目录.
   *          例如dd.getRequest().getSession().getServletContext().getRealPath("WEB-INF/uploadFiles")
   * @throws IOException
   */
  public MultipartRequest(HttpServletRequest servletrequest, String s)
      throws IOException, FileTooLargeException {
    this(servletrequest, s, FormatSettings.FIlESIZE);
  }

  /**
   * @param servletrequest
   * @param s
   *          上传后暂存的文件目录.
   *          例如dd.getRequest().getSession().getServletContext().getRealPath("WEB-INF/uploadFiles")
   * @param i
   *          上传大小限制(字节)
   * @throws IOException
   */
  public MultipartRequest(HttpServletRequest servletrequest, String s, int i)
      throws IOException, FileTooLargeException {
    parameters = new Hashtable();
    files = new Hashtable();
    //////////////////////
    typedFiles = new Hashtable();
    //////////////////////
    if (servletrequest == null)
      throw new IllegalArgumentException("request can not be null");
    if (s == null)
      throw new IllegalArgumentException("saveDirectory can not be null");
    if (i <= 0)
      throw new IllegalArgumentException("maxPostsize must be positive");
    req = servletrequest;
    dir = new File(s);
    maxSize = i;
    if (!dir.exists()) {
      dir.mkdirs();
    }
    if (!dir.isDirectory())
      throw new IllegalArgumentException("not a directory: " + s);
    if (!dir.canWrite()) {
      throw new IllegalArgumentException("not writable: " + s);
    } else {
      readRequest();
      return;
    }
  }

  public class UploadedFile {

    private String dir;
    private String filename;
    private String type;

    public String getContentType() {
      return type;
    }

    public String getFilesystemName() {
      return filename;
    }

    public File getFile() {
      if (dir == null || filename == null)
        return null;
      else
        return new File(dir + File.separator + filename);
    }

    UploadedFile(String s, String s1, String s2) {
      dir = s;
      filename = s1;
      type = s2;
    }
  }

  class MultipartInputStreamHandler {

    private ServletInputStream in;
    // private String boundary;
    private int totalExpected;
    private int totalRead;
    private byte buf[];
    private String characterEncoding;

    public String readLine() throws IOException {
      StringBuffer stringbuffer = new StringBuffer();
      int i;
      do {
        i = readLine(buf, 0, buf.length);
        if (i != -1)
          stringbuffer.append(new String(buf, 0, i, characterEncoding));
      } while (i == buf.length);
      if (stringbuffer.length() == 0) {
        return null;
      } else {
        stringbuffer.setLength(stringbuffer.length() - 2);
        return stringbuffer.toString();
      }
    }

    public int readLine(byte abyte0[], int i, int j) throws IOException {
      if (totalRead >= totalExpected)
        return -1;
      int k = in.readLine(abyte0, i, j);
      if (k > 0)
        totalRead += k;
      return k;
    }

    public String getCharacterEncoding() {
      return characterEncoding;
    }

    public MultipartInputStreamHandler(ServletInputStream servletinputstream,
        String s, int i, String s1) {
      in = null;
      // boundary = null;
      totalRead = 0;
      buf = new byte[8192];
      characterEncoding = null;
      in = servletinputstream;
      // boundary = s;
      totalExpected = i;
      characterEncoding = s1 == null ? "UTF-8" : s1;
    }
  }

}
